#pragma comment(lib,"glfwdll.lib")
#pragma comment(lib,"opengl32.lib")
#pragma comment( lib, "glew32.lib" )

#define _USE_MATH_DEFINES
#include <math.h>

#include <GL\glew.h>
#define GLFW_NO_GLU
#include <gl\glfw.h>

#include "OpenGLDebug.h"
#include "Shader.h"

#include <sstream>
#include <fstream>
#include <vector>
#include <algorithm>

#include "escapi.h"

struct POINT2D
{
	float x;
	float y;
};


struct MouseInfo
{
	// A struct to hold mouse data
public:
	int Mx,My;
	bool Mleft, Mright, Mmiddle;

	MouseInfo()
	{
		Mx = My = 0;
		Mleft = Mright = Mmiddle = false;
	}

	MouseInfo copy()
	{
		MouseInfo ret;
		ret.Mx = Mx;
		ret.My = My;
		ret.Mleft = Mleft;
		ret.Mright = Mright;
		ret.Mmiddle = Mmiddle;
		return ret;
	}
};


struct SimpleCapParams capture;
int device;

int xres = 640;
int yres = 480;

int videoWidth = 320;
int videoHeight = 240;

GLuint screenCaptureTexture;

POINT2D objectCenter;

std::vector<POINT2D> paintLine(1000);
//std::vector<POINT2D> paintLine;

bool Keys[GLFW_KEY_LAST] = {false};  // Key monitor
MouseInfo Mouse; // Mouse monitor
MouseInfo oldMouse;

// Handle keys - updates the Keys array
void KeyCallback(int key, int action)
{
	Keys[key] = (action == GLFW_PRESS);
}

void MouseButtonCallback(int button, int action)
{
	if (button == GLFW_MOUSE_BUTTON_LEFT)
		Mouse.Mleft = (action == GLFW_PRESS);
	else if (button == GLFW_MOUSE_BUTTON_RIGHT)
		Mouse.Mright = (action == GLFW_PRESS); 
	else if (button == GLFW_MOUSE_BUTTON_MIDDLE)
		Mouse.Mmiddle = (action == GLFW_PRESS);     
}

void MousePosCallback(int x, int y)
{
	Mouse.Mx = x;
	Mouse.My = y;
}

void InitWindow(int ScrX, int ScrY, int BPP)
{
	// InitWindow based on GP GLFW tutorial
	// http://gpwiki.org/index.php/OpenGL:Tutorials:Tutorial_Framework:Base#Framework.h_2
	// Initialize the window, may throw if something goes wrong.
	// Initialize the GLFW library
	if (glfwInit() != GL_TRUE)
		throw "Failed to initialize GLFW.";

	// Create a window (8-bit depth-buffer, no alpha and stencil buffers, windowed)
	if (glfwOpenWindow(ScrX, ScrY, BPP/3, BPP/3, BPP/3, 0, 8, 0, GLFW_WINDOW) != GL_TRUE)
		throw "Failed to open window.";

	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		printf("GLEW ERROR: %s\n", glewGetErrorString(err));
		throw "GLEW Init error";
	}

	if( !GLEW_VERSION_2_1)
	{
		throw "You must have OpenGL 2.1 support to run this program!\n";
	}

	// Give the window a title
	glfwSetWindowTitle("OpenGL Template");

	// Register event callbacks
	glfwSetKeyCallback(KeyCallback);
	glfwSetMouseButtonCallback(MouseButtonCallback);
	glfwSetMousePosCallback(MousePosCallback);
}

void drawQuad(float tx, float ty, float width, float height)
{
	glPushMatrix();
	glTranslatef(tx,ty,0);
	glBegin(GL_QUADS);
	glTexCoord2f(0,0); glVertex2f(0,0);
	glTexCoord2f(1,0); glVertex2f(width,0);
	glTexCoord2f(1,1); glVertex2f(width,height);
	glTexCoord2f(0,1); glVertex2f(0,height);
	glEnd();
	glPopMatrix();
}

void centroidFromTexture(GLubyte *pixels, POINT2D &centroid)
{
	float currentXTotal = 0;
	float currentYTotal = 0;
	float currentPixelTotal = 0;

	//int x = 0;
	//int y = 0;

	for(int currentPixel=0; currentPixel<(xres * yres); currentPixel++)
	{
		//GL_RGBA
		float r = pixels[currentPixel*4+ 0] / 255.0f;
		float g = pixels[currentPixel*4+ 1] / 255.0f;
		float b = pixels[currentPixel*4+ 2] / 255.0f;
		float a = pixels[currentPixel*4+ 3] / 255.0f;

		currentXTotal = currentXTotal + r;
		currentYTotal = currentYTotal + g;
		currentPixelTotal = currentPixelTotal + b;

		//'	setcolor r*255,g*255,b*255
		//'	plot x,y
		//'	x = x+1
		//'	if( x > xres-1)
		//'		x = 0
		//'		y = y+1
		//'endif
	}

	centroid.x = 1.0f - (currentXTotal / currentPixelTotal);
	centroid.y = 1.0f - currentYTotal / currentPixelTotal;
}
void fullScreenRender()
{
	//glDisable(GL_DEPTH_TEST);
	//glMatrixMode(GL_PROJECTION);
	//glLoadIdentity();
	//glMatrixMode(GL_MODELVIEW);
	//glLoadIdentity();

	glColor3ub(255,255,255);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, screenCaptureTexture);
	glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0,0, xres, yres, 0);

	glClear(GL_COLOR_BUFFER_BIT);
	glBegin(GL_QUADS);
	glMultiTexCoord2f(GL_TEXTURE0,0,1); glVertex2i(0,0); //y texcoords are flipped from what you may be used to. compensation for glCopyTexImage2D
	glMultiTexCoord2f(GL_TEXTURE0,1,1); glVertex2i(xres,0);
	glMultiTexCoord2f(GL_TEXTURE0,1,0); glVertex2i(xres,yres);
	glMultiTexCoord2f(GL_TEXTURE0,0,0); glVertex2i(0,yres);
	glEnd();

	//glEnable(GL_DEPTH_TEST);
}


void mainloop()
{
	// Create fullscreen render texture
	glActiveTexture(GL_TEXTURE0);
	glEnable(GL_TEXTURE_2D);
	glGenTextures(1, &screenCaptureTexture);
	glBindTexture(GL_TEXTURE_2D, screenCaptureTexture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	// Create video render texture
	glEnable(GL_TEXTURE_2D);
	GLuint videoTexture;
	glGenTextures(1,&videoTexture);
	glBindTexture(GL_TEXTURE_2D,videoTexture);

	// Load up some initial data
	glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,videoWidth,videoHeight,0,GL_RGBA,GL_UNSIGNED_BYTE,(GLvoid*)capture.mTargetBuf);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // Linear Filtering
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // Linear Filtering

	// Shaders
	Shader *standardShader = new Shader();
	standardShader->install("shaders/hipass.vert", "shaders/hipass.frag");
	glUseProgram(0);

	// glubyte always eq unsigned char?
	std::vector<GLubyte>frameBufferPixels( xres*yres*4);

	glfwSwapInterval(0); // vsync?

	float dt = 0.01f;
	float time = (float)glfwGetTime();
	float secondTimer = time+1.0f;
	int fps = 0;
	int frameCount = 0;
	int numframes = 0;
	int running = true;
	std::stringstream ss;
	while( running )
	{
		double now = glfwGetTime();
		dt =(float) now - time;
		time =(float) now;
		if( secondTimer < time )
		{
			fps = frameCount;
			frameCount = 0;
			secondTimer = (float)time+1.0f;

			ss << "fps :" << fps;
			std::string title = ss.str();
			ss.str("");
			glfwSetWindowTitle( title.c_str() );
		}
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho( 0,xres,yres,0,-1,1 );
		glMatrixMode( GL_MODELVIEW );
		glLoadIdentity();
		glViewport( 0,0,xres,yres );
		glClearColor(0,.6f,0,1);
		glClear(GL_COLOR_BUFFER_BIT);
		//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glEnable(GL_TEXTURE_2D);
		glColor3f(1,1,1);
		glBindTexture(GL_TEXTURE_2D, videoTexture );
		drawQuad(0,0,(float)xres, (float)yres);
		

		standardShader->begin();
		int UNIFORM_VIDEOFRAME = glGetUniformLocation( standardShader->getProgram(), "videoFrame");
		glUniform1i(UNIFORM_VIDEOFRAME, 0);	 // tex unit 0
		fullScreenRender();
		standardShader->end();

		glBindTexture(GL_TEXTURE_2D, screenCaptureTexture );
		glReadPixels(0,0, xres, yres, GL_RGBA, GL_UNSIGNED_BYTE, &frameBufferPixels[0]);
		centroidFromTexture(&frameBufferPixels[0], objectCenter);
		//glDisable(GL_TEXTURE_2D);

		if( !Mouse.Mleft )
		{
			glBindTexture(GL_TEXTURE_2D, videoTexture );
			//drawQuad(0,0,(float)xres, (float)yres);
			float tx=0; float ty=0; float width=(float)xres; float height = (float)yres;
			glPushMatrix();
			glTranslatef(tx,ty,0);
			glBegin(GL_QUADS);
			glTexCoord2f(1,0); glVertex2f(0,0);
			glTexCoord2f(0,0); glVertex2f(width,0);
			glTexCoord2f(0,1); glVertex2f(width,height);
			glTexCoord2f(1,1); glVertex2f(0,height);
			glEnd();
			glPopMatrix();

		}

		//glEnable(GL_TEXTURE_2D);
		//glBindTexture(GL_TEXTURE_2D, screenCaptureTexture );
		//drawQuad(0,0,xres,yres);

		glDisable(GL_TEXTURE_2D);
		glColor3f(255,0,0);
		glRectf(xres*objectCenter.x-5, yres*objectCenter.y-5, xres*objectCenter.x+5, yres*objectCenter.y+5);


		// "Delete" linestrip. Justs moves all points out if frame
		if( Keys[GLFW_KEY_DEL] )
		{
			for(size_t i=0; i<paintLine.size(); i++)
			{
				paintLine[i].x = -1;
				paintLine[i].y = -1;
			}
		}

		if( Keys[GLFW_KEY_SPACE])
		{
			POINT2D newP;
			newP.x = xres*objectCenter.x;
			newP.y = yres*objectCenter.y;
			paintLine.front().x = newP.x;
			paintLine.front().y = newP.y;

			for(int i=paintLine.size()-1; i>0; i--)
			{
				paintLine[i] = paintLine[i-1];
			}
		}




		glLineWidth(1.8f);
		glColor3f(1,1,0);
		glBegin(GL_LINE_STRIP);
		for(size_t i=0; i<paintLine.size()-1; i++)
		{
			glVertex2f( paintLine[i].x, paintLine[i].y );
		}
		glEnd();

		frameCount++;
		numframes++;
		bool glErr = checkGLErrors();
		if(glErr)
		{
			printf("gl error\n");
		}
		glfwSwapBuffers();
		//glfwSleep(1.0 / 1000.0); // 1 msec

		if(Keys['R'])
		{
			standardShader->reload();
		}

		int i;
		if (isCaptureDone(device))
		{
			// OpenGL red and blue are swapped, so we'll need to do some converting here..
			for (i = 0; i < videoWidth*videoHeight; i++)
				capture.mTargetBuf[i] = (capture.mTargetBuf[i] & 0xff00ff00) |
				((capture.mTargetBuf[i] & 0xff) << 16) |
				((capture.mTargetBuf[i] & 0xff0000) >> 16);

			// Load up the new data
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, videoTexture );
			glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,videoWidth,videoHeight,0,GL_RGBA,GL_UNSIGNED_BYTE,(GLvoid*)capture.mTargetBuf);

			// ..and ask for more
			doCapture(device);
		}

		

		if( glfwGetKey(GLFW_KEY_ESC) || !glfwGetWindowParam(GLFW_OPENED) )
		{
			running = false;
		}
	}

}

void initEscapi()
{

	device = setupESCAPI();
	if (device < 1)
	{
		fprintf(stderr, "Unable to init ESCAPI\n");
		exit(1);
	}

	// Use the second device if more than one found
	// (makes my personal test setup easier =)
	if (device > 1)
		device = 1;
	else
		device = 0;

	// Setup ESCAPI to capture a 256x256 image.
	capture.mWidth = videoWidth;
	capture.mHeight = videoHeight;
	capture.mTargetBuf = new int[videoWidth*videoHeight];
	initCapture(device,&capture);
	doCapture(device);
}

int main()
{
	initEscapi();

	int retval = 0;
	try
	{
		// Initialize the window
		InitWindow(xres,yres,24);
		// Pass control to the render function
		mainloop();
	}
	catch (const char* error)
	{
		// Report an error
		std::ofstream error_file("GL_ERROR.txt");
		error_file << "Caught exception:\n  " << error << '\n';
		retval = 1;
	}

	glfwTerminate();
	return retval;
}